/*
 * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
 *
 * openInula is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
/**
 * @jsxImportSource @openinula/next
 */

import { describe, expect, vi } from 'vitest';
import { domTest as it } from './utils';
import { render } from '../../src';

vi.mock('../../src/scheduler', async () => {
  return {
    schedule: (task: () => void) => {
      task();
    },
  };
});
describe('for', () => {
  it('should work', ({ container }) => {
    function App() {
      const arr = [0, 1, 2];
      return <for each={arr}>{item => <div>{item}</div>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('"<div>0</div><div>1</div><div>2</div>"');
  });

  it('should update item when arr changed', ({ container }) => {
    let updateArr: (num: number) => void;

    function App() {
      const arr = [0, 1, 2];
      updateArr = (num: number) => {
        arr.push(num);
      };
      return <for each={arr}>{item => <div>{item}</div>}</for>;
    }

    render(App(), container);
    expect(container.children.length).toEqual(3);
    updateArr(3);
    expect(container.children.length).toEqual(4);
    expect(container.innerHTML).toMatchInlineSnapshot('"<div>0</div><div>1</div><div>2</div><div>3</div>"');
  });

  it('should update item when arr changed with extra state', ({ container }) => {
    let updateArr: (num: number) => void;

    function App() {
      let extra = 'extra';
      const arr = [0, 1, 2];
      updateArr = (num: number) => {
        arr.push(num);
      };
      return <>
        <for each={arr}>{item => <div>{item}</div>}</for>
        <div>{extra}</div>
      </>;
    }

    render(App(), container);
    expect(container.children.length).toEqual(4);
    updateArr(3);
    expect(container.children.length).toEqual(5);
    expect(container.innerHTML).toMatchInlineSnapshot(`"<div>0</div><div>1</div><div>2</div><div>3</div><div>extra</div>"`);
  });

  it('should get index', ({ container }) => {
    let update: (num: number) => void;
    function App() {
      const arr = [0, 1, 2];
      update = (num: number) => {
        arr.push(num);
      };
      return <for each={arr}>{(item, index) => <div>{index}</div>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('"<div>0</div><div>1</div><div>2</div>"');
    update(3);
    expect(container.innerHTML).toMatchInlineSnapshot('"<div>0</div><div>1</div><div>2</div><div>3</div>"');
  });
  // Compile Error
  // it.fails('should transform for loop', ({ container }) => {
  //   function MyComp() {
  //     let name = 'test';
  //     let arr = [
  //       { x: 1, y: 1 },
  //       { x: 2, y: 2 },
  //       { x: 3, y: 3 },
  //     ];
  //     return (
  //       <for each={arr}>
  //         {({ x, y }, index) => {
  //           let name1 = 'test';
  //           const onClick = () => {
  //             name1 = 'changed';
  //           };
  //           return (
  //             <div onClick={onClick} id={`item${index}`}>
  //               {name1}
  //               {index}
  //             </div>
  //           );
  //         }}
  //       </for>
  //     );
  //   }
  //
  //   render(MyComp(), container);
  //   expect(container.innerHTML).toBe(
  //     '<div id="item0">test0</div><div id="item1">test1</div><div id="item2">test2</div>'
  //   );
  //   const item = container.querySelector('#item0') as HTMLDivElement;
  //   item.click();
  //   expect(container.innerHTML).toBe(
  //     '<div id="item0">changed0</div><div id="item1">test1</div><div id="item2">test2</div>'
  //   );
  // });

  it('should transform map to for jsx element', ({ container }) => {
    function MyComp() {
      const arr = [1, 2, 3];
      return (
        <>
          {arr.map(item => (
            <div>{item}</div>
          ))}
        </>
      );
    }

    render(MyComp(), container);
    expect(container.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>');
  });
  it('should transform map in map to for', ({ container }) => {
    function MyComp() {
      const matrix = [
        [1, 2],
        [3, 4],
      ];
      return <div>{matrix.map(arr => arr.map(item => <div>{item}</div>))}</div>;
    }

    render(MyComp(), container);
    expect(container.innerHTML).toBe('<div><div>1</div><div>2</div><div>3</div><div>4</div></div>');
  });

  // Compile error
  // it.fails('should transform last map to for" ', ({ container }) => {
  //   function MyComp() {
  //     let arr = [1, 2, 3];
  //     return (
  //       <div>
  //         {arr
  //           .map(item => <div>{item}</div>)
  //           .map(item => (
  //             <div>{item}</div>
  //           ))}
  //       </div>
  //     );
  //   }
  //
  //   render(MyComp(), container);
  //   expect(container.innerHTML).toBe('<div><div>1</div><div>2</div><div>3</div><div>4</div></div>');
  // });
  it('Should correctly render a single-level loop of elements', ({ container }) => {
    function App() {
      const fruits = ['Apple', 'Banana', 'Cherry'];
      return <for each={fruits}>{fruit => <li>{fruit}</li>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('"<li>Apple</li><li>Banana</li><li>Cherry</li>"');
  });

  it('Should correctly render nested loops of elements', ({ container }) => {
    function App() {
      const matrix = [
        [1, 2],
        [3, 4],
        [5, 6],
      ];
      return (
        <for each={matrix}>
          {row => (
            <div>
              <for each={row}>{cell => <span>{cell}</span>}</for>
            </div>
          )}
        </for>
      );
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<div><span>1</span><span>2</span></div><div><span>3</span><span>4</span></div><div><span>5</span><span>6</span></div>"'
    );
  });

  it('Should correctly render loops with complex data structures', ({ container }) => {
    function App() {
      const users = [
        { id: 1, name: 'Alice', hobbies: ['reading', 'gaming'] },
        { id: 2, name: 'Bob', hobbies: ['cycling', 'photography'] },
      ];
      return (
        <for each={users}>
          {user => (
            <div>
              <h2>{user.name}</h2>
              <ul>
                <for each={user.hobbies}>{hobby => <li>{hobby}</li>}</for>
              </ul>
            </div>
          )}
        </for>
      );
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<div><h2>Alice</h2><ul><li>reading</li><li>gaming</li></ul></div><div><h2>Bob</h2><ul><li>cycling</li><li>photography</li></ul></div>"'
    );
  });

  it('Should correctly render when for tag input is an array map', ({ container }) => {
    function App() {
      const numbers = [1, 2, 3, 4, 5];
      return <for each={numbers.map(n => n * 2)}>{doubledNumber => <span>{doubledNumber}</span>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<span>2</span><span>4</span><span>6</span><span>8</span><span>10</span>"'
    );
  });

  it('Should update single item', ({ container }) => {
    let updateNumber: (n: number) => void;

    function App() {
      const numbers = [1, 2, 3];
      updateNumber = n => {
        numbers[0] = n;
      };
      return <for each={numbers}>{number => <span>{number}</span>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('"<span>1</span><span>2</span><span>3</span>"');
    updateNumber!(4);
    expect(container.innerHTML).toMatchInlineSnapshot('"<span>4</span><span>2</span><span>3</span>"');
  });

  it('Should handle item dependencies changed', ({ container }) => {
    let addColor: () => void;
    let changeCounter: () => void;

    function Colors() {
      let a = 1;
      const colors = ['red', 'green', 'blue'];
      
      addColor = () => {
        colors.push('yellow');
      };
      
      changeCounter = () => {
        a += 1;
      };
      
      return (
        <ul>
          <for each={colors}>{color => <li>{color}{a}</li>}</for>
          <button onClick={addColor}>add{a}</button>
          <button onClick={changeCounter}>change{a}</button>
        </ul>
      );
    }

    render(Colors(), container);
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<ul><li>red1</li><li>green1</li><li>blue1</li><button>add1</button><button>change1</button></ul>"'
    );
    
    addColor();
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<ul><li>red1</li><li>green1</li><li>blue1</li><li>yellow1</li><button>add1</button><button>change1</button></ul>"'
    );
    
    changeCounter();
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<ul><li>red2</li><li>green2</li><li>blue2</li><li>yellow2</li><button>add2</button><button>change2</button></ul>"'
    );
  });

  it('Should handle empty array', ({ container }) => {
    function App() {
      const items = [];
      return <for each={items}>{item => <div>{item}</div>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('""');
  });

  it('Should handle array removal operations', ({ container }) => {
    let removeItem: (index: number) => void;

    function App() {
      const items = [1, 2, 3, 4, 5];
      removeItem = (index: number) => {
        items.splice(index, 1);
      };
      return <for each={items}>{item => <span>{item}</span>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('"<span>1</span><span>2</span><span>3</span><span>4</span><span>5</span>"');
    
    removeItem(2);
    expect(container.innerHTML).toMatchInlineSnapshot('"<span>1</span><span>2</span><span>4</span><span>5</span>"');
    
    removeItem(0);
    expect(container.innerHTML).toMatchInlineSnapshot('"<span>2</span><span>4</span><span>5</span>"');
  });

  it('Should handle array clear operation', ({ container }) => {
    let clearItems: () => void;

    function App() {
      const items = [1, 2, 3];
      clearItems = () => {
        items.length = 0;
      };
      return <for each={items}>{item => <div>{item}</div>}</for>;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot('"<div>1</div><div>2</div><div>3</div>"');
    
    clearItems();
    expect(container.innerHTML).toMatchInlineSnapshot('""');
  });

  it('Should handle optional member expression with map (comment replies scenario)', ({ container }) => {
    interface Reply {
      id: number;
      content: string;
    }

    interface Comment {
      id: number;
      content: string;
      replies?: Reply[];
    }

    function CommentComponent({ comment, depth = 0 }: { comment: Comment; depth?: number }) {
      return (
        <div style={{ marginLeft: `${depth * 20}px` }}>
          <div>Comment: {comment.content}</div>
          {comment.replies?.map(reply => (
            <CommentComponent 
              key={reply.id} 
              comment={reply} 
              depth={depth + 1} 
            />
          ))}
        </div>
      );
    }

    function App() {
      const comment: Comment = {
        id: 1,
        content: 'Root comment',
        replies: [
          { id: 2, content: 'First reply' },
          { id: 3, content: 'Second reply' }
        ]
      };

      return <CommentComponent comment={comment} />;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<div style=\"margin-left: 0px;\"><div>Comment: Root comment</div><div style=\"margin-left: 20px;\"><div>Comment: First reply</div></div><div style=\"margin-left: 20px;\"><div>Comment: Second reply</div></div></div>"'
    );
  });

  it('Should handle optional member expression with undefined array', ({ container }) => {
    interface Comment {
      id: number;
      content: string;
      replies?: { id: number; content: string }[];
    }

    function CommentComponent({ comment }: { comment: Comment }) {
      return (
        <div>
          <div>Comment: {comment.content}</div>
          {comment.replies?.map(reply => (
            <div key={reply.id}>Reply: {reply.content}</div>
          ))}
        </div>
      );
    }

    function App() {
      const comment: Comment = {
        id: 1,
        content: 'Root comment without replies'
      };

      return <CommentComponent comment={comment} />;
    }

    render(App(), container);
    expect(container.innerHTML).toMatchInlineSnapshot(
      '"<div><div>Comment: Root comment without replies</div></div>"'
    );
  });
});
